Simply typed lambda calculus

The simply typed lambda calculus (\lambda^\to), a form of type theory, is a typed interpretation of the lambda calculus with only one type constructor: \to that builds function types. It is the canonical and simplest example of a typed lambda calculus. The simply typed lambda calculus was originally introduced by Alonzo Church in 1940 as an attempt to avoid paradoxical uses of the untyped lambda calculus, and it exhibits many desirable and interesting properties.

The term simple type is also used to refer to extensions of the simply typed lambda calculus such as products, coproducts or natural numbers (System T) or even full recursion (like PCF). In contrast, systems which introduce polymorphic types (like System F) or dependent types (like the Logical Framework) are not considered simply typed. The former are still considered simple because the Church encodings of such structures can be done using only \to and suitable type variables, while polymorphism and dependency cannot.

Contents

Syntax

To define the types, we begin by fixing a set of base types, B. These are sometimes called atomic types or type constants. With this fixed, the syntax of types is:

\tau = \tau \to \tau \mid T \quad \mathrm{where} \; T \in B.

In this article, we use \sigma and \tau to range over types. Informally, the function type \sigma \to \tau refers to the set of functions that, given an input of type \sigma, produce an output of type \tau. By convention, \to associates to the right: we read \sigma\to\tau\to\rho as \sigma\to(\tau\to\rho).

We also fix a set of term constants for the base types. For example, we might assume a base type nat, and the term constants could be the natural numbers. In the original presentation, Church used only two base types: o for "the type of propositions" and \iota for "the type of individuals". The type o has no term constants, whereas \iota has one term constant. Frequently the calculus with only one base type, usually o, is considered.

The syntax of the simply typed lambda calculus is essentially that of the lambda calculus itself. The term syntax used in this article is as follows:

e = x \mid \lambda x\!:\!\tau.e \mid e \, e \mid c

where c is a term constant.

That is, variable reference, abstractions, application, and constant. A variable reference x is bound if it is inside of an abstraction binding x. A term is closed if there are no unbound variables.

Compare this to the syntax of untyped lambda calculus:

e = x \mid \lambda x.e \mid e \, e

We see that in typed lambda calculus every function (abstraction) must specify the type of its argument.

Typing rules

To define the set of well typed lambda terms of a given type, we will define a typing relation between terms and types. First, we introduce typing contexts \Gamma,\Delta,\dots, which are sets of typing assumptions. A typing assumption has the form x:\sigma, meaning x has type \sigma.

The typing relation \Gamma\vdash e�: \sigma indicates that e is a term of type \sigma in context \Gamma. It is therefore said that "e is well-typed (at \sigma)". Instances of the typing relation are called typing judgments. The validity of a typing judgment is shown by providing a typing derivation, constructed using the following typing rules (wherein the premises above the line allow us to derive the conclusion below the line):

{x:\sigma \in \Gamma}\over{\Gamma \vdash x�: \sigma } (1)      {c \textrm{~is~a~constant~of~type~} T}\over{\Gamma\vdash c�: T} (2)
{\Gamma,x:\sigma\vdash e:\tau}\over{\Gamma\vdash \lambda x�: \sigma.~e�: \sigma \to \tau} (3)      {\Gamma\vdash e_1:\sigma\to\tau\quad\Gamma\vdash e_2:\sigma}\over{\Gamma\vdash e_1~e_2�: \tau} (4)

In other words,

  1. If x has type \sigma in the context, we know that x has type \sigma.
  2. Term constants have the appropriate base types.
  3. If, in a certain context with x having type \sigma, e has type \tau, then, in the same context without x, \lambda x�: \sigma.~e has type \sigma \to \tau.
  4. If, in a certain context, e_1 has type \sigma \to \tau, and e_2 has type \sigma, then e_1~e_2 has type \tau.

Examples of closed terms, i.e. terms typable in the empty context, are:

These are the typed lambda calculus representations of the basic combinators of combinatory logic.

Each type \tau is assigned an order, a number o(\tau). For base types, o(T)=0; for function types, o(\sigma\to\tau)=\mbox{max}(o(\sigma)%2B1,o(\tau)). That is, the order of a type measures the depth of the most left-nested arrow. Hence:

o(\iota \to \iota \to \iota) = 1
o((\iota \to \iota) \to \iota) = 2

Semantics

Intrinsic vs. extrinsic interpretations

Broadly speaking, there are two different ways of assigning meaning to the simply typed lambda calculus, as to typed languages more generally, sometimes called intrinsic vs. extrinsic, or Church-style vs. Curry-style.[1] An intrinsic/Church-style semantics only assigns meaning to well-typed terms, or more precisely, assigns meaning directly to typing derivations. This has the effect that terms differing only by type annotations can nonetheless be assigned different meanings. For example, the identity term \lambda x:\texttt{int}.~x on integers and the identity term \lambda x:\texttt{bool}.~x on booleans may mean different things. (The classic intended interpretations are the identity function on integers and the identity function on boolean values.) In contrast, an extrinsic/Curry-style semantics assigns meaning to terms regardless of typing, as they would be interpreted in an untyped language. In this view, \lambda x:\texttt{int}.~x and \lambda x:\texttt{bool}.~x mean the same thing (i.e., the same thing as \lambda x.~x).

The distinction between intrinsic and extrinsic semantics is sometimes associated with the presence or absence of annotations on lambda abstractions, but strictly speaking this usage is imprecise. It is possible to define a Curry-style semantics on annotated terms simply by ignoring the types (i.e., through type erasure), as it is possible to give a Church-style semantics on unannotated terms when the types can be deduced from context (i.e., through type inference). The essential difference between intrinsic and extrinsic approaches is just whether the typing rules are viewed as defining the language, or as a formalism for verifying properties of a more primitive underlying language. Most of the different semantic interpretations discussed below can be seen through either Church or Curry goggles.

Equational theory

The simply typed lambda calculus has the same theory of βη-equivalence as untyped lambda calculus, but subject to type restrictions. The equation (\lambda x:\sigma.t)\,u =_{\beta} t[x:=u] holds in context \Gamma whenever \Gamma,x:\sigma \vdash t:\tau and \Gamma\vdash u:\sigma, while the equation t =_\eta \lambda x:\sigma.t\,x holds whenever \Gamma\vdash t:\sigma \to \tau.

Operational semantics

Likewise, the operational semantics of simply typed lambda calculus can be fixed as for the untyped lambda calculus, using call by name, call by value, or other evaluation strategies. As for any typed language, type safety is a fundamental property of all of these evaluation strategies. Additionally, the strong normalization property described below implies that any evaluation strategy will terminate on all simply typed terms.

Categorical semantics

The simply typed lambda calculus (with \beta\eta-equivalence) is the internal language of Cartesian Closed Categories (CCCs), as was first observed by Lambek.

Proof-theoretic semantics

The simply typed lambda calculus is closely related to the implicational fragment of propositional intuitionistic logic (i.e., minimal logic) via the Curry–Howard isomorphism: terms correspond precisely to proofs in natural deduction, and inhabited types are exactly the tautologies of minimal logic.

Alternative syntaxes

The presentation given above is not the only way of defining the syntax of the simply typed lambda calculus. One alternative is to remove type annotations entirely (so that the syntax is identical to the untyped lambda calculus), while ensuring that terms are well-typed via Hindley-Milner type inference. The inference algorithm is terminating, sound, and complete: whenever a term is typable, the algorithm computes its type. More precisely, it computes the term's principal type, since often an unannotated term (such as \lambda x.~x) may have more than one type (\texttt{int} \to \texttt{int}, \texttt{bool} \to \texttt{bool}, etc., which are all instances of the principal type \alpha \to \alpha).

Another alternative presentation of simply typed lambda calculus is based on bidirectional type checking, which requires more type annotations than Hindley-Milner inference but is easier to describe. The type system is divided into two judgments, representing both checking and synthesis, written \Gamma \vdash e \Leftarrow \tau and \Gamma \vdash e \Rightarrow \tau respectively. Operationally, the three components \Gamma, e, and \tau are all inputs to the checking judgment \Gamma \vdash e \Leftarrow \tau, whereas the synthesis judgment \Gamma \vdash e \Rightarrow \tau only takes \Gamma and e as inputs, producing the type \tau as output. These judgments are derived via the following rules:

{x:\sigma \in \Gamma}\over{\Gamma \vdash x \Rightarrow \sigma } [1]      {c \textrm{~is~a~constant~of~type~} T}\over{\Gamma\vdash c \Rightarrow T} [2]
{\Gamma,x:\sigma\vdash e\Leftarrow \tau}\over{\Gamma\vdash \lambda x.~e \Leftarrow \sigma \to \tau} [3]      {\Gamma\vdash e_1\Rightarrow \sigma\to\tau\quad\Gamma\vdash e_2\Leftarrow\sigma}\over{\Gamma\vdash e_1~e_2 \Rightarrow \tau} [4]
{\Gamma\vdash e \Rightarrow \tau}\over{\Gamma\vdash e\Leftarrow \tau} [5]      {\Gamma\vdash e \Leftarrow \tau}\over{\Gamma\vdash (e:\tau)\Rightarrow \tau} [6]

Observe that rules [1]–[4] are nearly identical to rules (1)–(4) above, except for the careful choice of checking or synthesis judgments. These choices can be explained like so:

  1. If x:\sigma is in the context, we can synthesize type \sigma for x.
  2. The types of term constants are fixed and can be synthesized.
  3. To check that \lambda x.~e has type \sigma \to \tau in some context, we extend the context with x:\sigma and check that e has type \tau.
  4. If e_1 synthesizes type \sigma \to \tau (in some context), and e_2 checks against type \sigma (in the same context), then e_1~e_2 synthesizes type \tau.

Observe that the rules for synthesis are read top-to-bottom, whereas the rules for checking are read bottom-to-top. Note in particular that we do not need any annotation on the lambda abstraction in rule [3], because the type of the bound variable can be deduced from the type at which we check the function. Finally, we explain rules [5] and [6] as follows:

  1. To check that e has type \tau, it suffices to synthesize type \tau.
  2. If e checks against type \tau, then the explicitly annotated term (e:\tau) synthesizes \tau.

Because of these last two rules coercing between synthesis and checking, it is easy to see that any well-typed but unannotated term can be checked in the bidirectional system, so long as we insert "enough" type annotations. And in fact, annotations are needed only at β-redexes.

General observations

Given the standard semantics, the simply typed lambda calculus is strongly normalizing: that is, well-typed terms always reduce to a value, i.e., a \lambda abstraction. This is because recursion is not allowed by the typing rules: it is impossible to find types for fixed-point combinators and the looping term \Omega = (\lambda x.~x~x) (\lambda x.~x~x). Recursion can be added to the language by either having a special operator \texttt{fix}_\alphaof type (\alpha \to \alpha) \to \alpha or adding general recursive types, though both eliminate strong normalization.

Since it is strongly normalizing, it is decidable whether or not a simply typed lambda calculus program halts: it does! We can therefore conclude that the language is not Turing complete.

Important results

Notes

  1. ^ Reynolds, John (1998). Theories of Programming Languages. Cambridge, England: Cambridge University Press. 

References

See also

External links